// ChessController.cpp
//
// Created By: Jeremy Butler
// Created On: 23-Mar-2011

#include "ChessController.h"
#include "Move.h"
#include "ChessXMLParser.h"
#include "ChessXMLGenerator.h"
#include "HumanPlayer.h"
#include "ComputerPlayer.h"
#include "Game.h"

#include <set>
#include <typeinfo>
using namespace std;

ChessController::ChessController() : view(NULL), model(NULL), fileName(),
				     playerWhite(NULL), playerBlack(NULL),
				     currentPlayer(NULL), gameOver(true){
  return;
}

ChessController::ChessController(ChessView & _view, int gameType) :
    view(&_view),
    fileName(),
    model(new Game()),
    gameOver(false){

  if(gameType == 0){
    playerWhite = new HumanPlayer(WHITE);
    playerBlack = new HumanPlayer(BLACK);
  }
  else if(gameType == 1){
    playerWhite = new HumanPlayer(WHITE);
    playerBlack = new ComputerPlayer(BLACK, this);
  }
  else if(gameType == 2){
    playerWhite = new ComputerPlayer(WHITE, this);
    playerBlack = new HumanPlayer(BLACK);
  }
  else if(gameType == 3){
    playerWhite = new ComputerPlayer(WHITE, this);
    playerBlack = new ComputerPlayer(BLACK, this);
  }
  currentPlayer = playerBlack;//this is so that when the game starts, it is white's turn.
}

ChessController::ChessController(const ChessController & other) :
    view(other.view),
    model(other.model),
    fileName(other.fileName),
    playerWhite(other.playerWhite),
    playerBlack(other.playerBlack),
    currentPlayer(other.currentPlayer),
    gameOver(other.gameOver){
  return;
}

ChessController & ChessController::operator = (const ChessController & other){
  if(this != &other){
    view = other.view;
    model = other.model;
    fileName = other.fileName;
    playerWhite = other.playerWhite;
    playerBlack = other.playerBlack;
    currentPlayer = other.currentPlayer;
    gameOver = other.gameOver;
  }
  return *this;
}

ChessController::~ChessController(){
  delete model;
  delete playerWhite;
  delete playerBlack;
}

void ChessController::on_NewGame(){

  LoadGame("games/newGame.xml");

}

void ChessController::on_QuitGame(){
  //Modify this method later if I want to implement save on exit
  return;
}

void ChessController::on_LoadGame(){

  gameOver = false;
  LoadGame(view->SelectLoadFile());

}

void ChessController::LoadGame(const string & fileName){
  ChessXMLParser parser;
  try{
    parser.Parse(fileName);    
  }catch(FileException e){
    cout << e.GetMessage() << endl;
    return;
  }

  ResetView();

  const vector<pair<Piece *, Location> > & pieces = parser.GetPieces();
  const deque<Move> & history = parser.GetHistory();

  for(int i = 0; i < pieces.size(); i++){

    Location location = (pieces[i]).second;
    Piece * piece = (pieces[i]).first;
    view->PlacePiece(location.GetY(),location.GetX(), GetImageName(*piece));

  }

  delete model;
  try{
    model = new Game(pieces, history);
  }catch(IllegalStateException e){
    view->WriteMessageArea(e.GetMessage() + "\n");
  }

  view->SetStatusBar(fileName + " Loaded Successfully");
  
  gameOver = false;

  if(history.empty()){
    currentPlayer = playerBlack;
  }
  else{
    Color lastColorToMove = history.back().GetToPiece()->GetColor();
    if(lastColorToMove == WHITE)
      currentPlayer = playerWhite;
    else if(lastColorToMove == BLACK)
      currentPlayer = playerBlack;
  }
  if(typeid(*playerBlack).name() == typeid(ComputerPlayer).name())
    dynamic_cast<ComputerPlayer*>(playerBlack)->Reset();
  if(typeid(*playerWhite).name() == typeid(ComputerPlayer).name())
    dynamic_cast<ComputerPlayer*>(playerWhite)->Reset();
  
  UpdateGameState();
}

ImageName ChessController::GetImageName(const Piece & piece){

  ImageName imageName;
  Color color = piece.GetColor();

  switch (piece.GetType()){
    case PAWN:
      imageName = (color == WHITE) ? W_PAWN : B_PAWN;
      break;
    case KNIGHT:
      imageName = (color == WHITE) ? W_KNIGHT : B_KNIGHT;
      break;
    case ROOK:
      imageName = (color == WHITE) ? W_ROOK : B_ROOK;
      break;
    case BISHOP:
      imageName = (color == WHITE) ? W_BISHOP : B_BISHOP;
      break;
    case KING:
      imageName = (color == WHITE) ? W_KING : B_KING;
      break;
    case QUEEN:
      imageName = (color == WHITE) ? W_QUEEN : B_QUEEN;
      break;
    default:
      break;
  }

  return imageName;

}

void ChessController::on_CellSelected(int row, int col, int button){
  if(gameOver || typeid(*currentPlayer).name() == typeid(ComputerPlayer).name())
    return;

  const Move * lastMove = model->MovePiece(row, col);
  if(lastMove){
    UnhighlightAll();
    int fromRow = model->GetSelectedSpace().GetY();
    int fromCol = model->GetSelectedSpace().GetX();

    view->ClearPiece(fromRow, fromCol);
    view->PlacePiece(row, col, GetImageName(*((*model)(row,col))));
    view->WriteMessageArea("\n" + lastMove->ToString() + "\n\n");
    UpdateGameState();
  }
  else{
    const Piece * selectedSpace = (*model)(row,col);
    if(!selectedSpace || selectedSpace->GetColor() != currentPlayer->GetColor()){
      model->SetSelectedSpace(-1,-1);
      UnhighlightAll();
    }
    else if(currentPlayer->GetColor() == selectedSpace->GetColor()){
      model->SetSelectedSpace(row,col);
      HighlightValidMoves(model->GetValidMoves(row,col),row,col);
    }
  }
}

void ChessController::on_DragStart(int row, int col){
  if(gameOver || typeid(*currentPlayer).name() == typeid(ComputerPlayer).name())
    return;

    const Piece * selectedSpace = (*model)(row,col);
    if(!selectedSpace || selectedSpace->GetColor() != currentPlayer->GetColor()){
      model->SetSelectedSpace(-1,-1);
      UnhighlightAll();
    }
    else if(currentPlayer->GetColor() == selectedSpace->GetColor()){
      model->SetSelectedSpace(row,col);
      HighlightValidMoves(model->GetValidMoves(row,col),row, col);
    }

}

bool ChessController::on_DragEnd(int row, int col){
  if(gameOver || typeid(*currentPlayer).name() == typeid(ComputerPlayer).name())
    return false;

  const Move * lastMove = model->MovePiece(row, col);

  if(lastMove){
    int fromRow = model->GetSelectedSpace().GetY();
    int fromCol = model->GetSelectedSpace().GetX();
    UnhighlightAll();
    view->ClearPiece(fromRow, fromCol);
    view->PlacePiece(row, col, GetImageName(*((*model)(row,col))));
    view->WriteMessageArea("\n" + lastMove->ToString() + "\n\n" );
    UpdateGameState();
    return true;
  }
  else{
    view->WriteMessageArea("\nInvalid move\n\n");
    return false;
  }

}

void ChessController::HighlightValidMoves(const set<Location> & validMoves, int row, int col){

  UnhighlightAll();
  view->HighlightSquare(row,col,RED_SQUARE);
  for(set<Location>::iterator it = validMoves.begin(); it != validMoves.end(); it++){
    view->HighlightSquare((*it).GetY(), (*it).GetX(), BLUE_SQUARE);
  }
}

void ChessController::UnhighlightAll(){

  for (int row = 0; row < 8; row++){
    for(int col = 0; col < 8; col++)
      view->UnHighlightSquare(row, col);
  }

}

void ChessController::on_UndoMove(){
  if(typeid(*currentPlayer).name() == typeid(ComputerPlayer).name())
    return;

  
  UnhighlightAll();
  
  Move * move = model->UndoMove();

  if(move){
    if(gameOver)
      gameOver = false;
    view->ClearPiece(move->GetToLocation().GetY(),move->GetToLocation().GetX());
    view->PlacePiece(move->GetFromLocation().GetY(),move->GetFromLocation().GetX(),
		      GetImageName(*(move->GetFromPiece())));
    if(move->GetTakenPiece()){
      view->PlacePiece(move->GetToLocation().GetY(),move->GetToLocation().GetX(),
			GetImageName(*(move->GetTakenPiece())));
    }
    UpdateGameState();
    delete move;
  }

}

void ChessController::on_SaveGame(){

  if(fileName == ""){
    fileName = view->SelectSaveFile();
    if(fileName != ""){
      SaveGame(fileName);
    }
  }
  else{
    SaveGame(fileName);
  }

}

void ChessController::on_SaveGameAs(){

  fileName = view->SelectSaveFile();
  if(fileName != ""){
    SaveGame(fileName);
  }

}

void ChessController::SaveGame(const string & fileName){

  ChessXMLGenerator xmlGen(model);
  xmlGen.GenerateChessXML(fileName);
  view->SetStatusBar(fileName + " Saved Successfully");

}

void ChessController::on_TimerEvent(){
  if(!gameOver && typeid(*currentPlayer).name() == typeid(ComputerPlayer).name()){
    currentPlayer->on_TimerEvent();
  }
}

void ChessController::SetView(IChessView* view){
  this->view = view;
}

void ChessController::UpdateGameState(){
  if(model->IsInCheck(WHITE) && model->IsInCheck(BLACK)){
    gameOver = true;
    return;
  }

  PrepareNextTurn();
  
  Color currentColor = currentPlayer->GetColor();

  view->SetTopLabel("");
  if(model->IsInCheck(currentColor)){
    if(model->IsInCheckMate(currentColor)){
      gameOver = true;
      string str = (currentColor == BLACK) ? "White " : "Black ";
      str += "Wins!!!";
      view->SetTopLabel(str);
    }
    else{
      view->SetTopLabel("CHECK!");
    }
  }
  if(model->StaleMate()){
    gameOver = true;
    view->SetTopLabel("Stale Mate!");
  }
}

void ChessController::ResetView(){
  for(int i = 0; i < 8; i++){
    for(int j = 0; j < 8; j++){
      view->ClearPiece(i,j);
    }
  }

  UnhighlightAll();
}

void ChessController::PrepareNextTurn(){
  Color currentColor = currentPlayer->GetColor();
  if(currentColor == WHITE){
    currentPlayer = playerBlack;
  }
  else if(currentColor == BLACK){
    currentPlayer = playerWhite;
  }

  if(currentColor == BLACK){
    view->SetBottomLabel("White's Turn");
  }
  else{
    view->SetBottomLabel("Black's Turn");
  }
}
